// 交叉引用列表：输出各个输入文件中标识符的交叉引用列表 .
// ./xref file1 file2 file3
// cat file1 | ./xref
#include "table.h"
#include "set.h"
#include "mem.h"
#include "atom.h"
#include "getword.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#define DESTROY_BY_MAP

static int linenum = 0;

static int
first(int c)
{
    if (c == '\n')
        linenum++;
    return isalpha(c) || c == '_';
}

static int
rest(int c)
{
    return isalnum(c) || c == '_';
}

static int
compare(const void *x, const void *y)
{
    return strcmp(*(char**)x, *(char**)y);
}

static int
cmpint(const void *x, const void *y)
{
    return **(int**)x - **(int**)y;
}

static int
intcmp(const void *x, const void *y)
{
    return cmpint(&x, &y);
}

static unsigned int
inthash(const void *x)
{
    return *(int*)x;
}

static void
xref(const char *name, FILE *fp, Table_T identifiers)
{
    if (name == NULL)
        name = "";
    name = Atom_string(name);

    char buf[10240];
    linenum = 1;
    while (getword(fp, buf, sizeof(buf), first, rest)) {
        const char *id = Atom_string(buf);
        Table_T files = Table_get(identifiers, id);
        if (files == NULL) {
            files = Table_new(0, NULL, NULL);
            Table_put(identifiers, id, files);
        }

        Set_T lines = Table_get(files, name);
        if (lines == NULL) {
            lines = Set_new(0, intcmp, inthash);
            Table_put(files, name, lines);
        }

        // 标识符在一行出现多次仅仅记录一次
        int *p = &linenum;
        if (!Set_member(lines, p)) {
            NEW(p);
            *p = linenum;
            Set_put(lines, p);
        }
    }
}

static void
printlines(Set_T lines)
{
    void **arr = Set_toArray(lines, NULL);
    qsort(arr, Set_length(lines), sizeof(*arr), cmpint);

    for (int i = 0; arr[i]; i++)
        printf(" %d", *(int*)arr[i]);
    FREE(arr);
}

static void
print(Table_T files)
{
    void **arr = Table_toArray(files, NULL);
    qsort(arr, Table_length(files), 2*sizeof(*arr), compare);

    for (int i = 0; arr[i]; i += 2) {
        if (*(char*)arr[i] != '\0')
            printf("\t%s:", (char*)arr[i]);
        printlines(arr[i+1]);
        printf("\n");
    }
    FREE(arr);
}

static void
dealresult(Table_T identifiers)
{
    void **arr = Table_toArray(identifiers, NULL);
    qsort(arr, Table_length(identifiers), 2*sizeof(*arr), compare);

    for (int i = 0; arr[i]; i += 2) {
        printf("%s", (char*)arr[i]);
        print(arr[i+1]);
    }
    FREE(arr);
}

static void
free_line(const void *member, void *cl)
{
    (void)cl;
    int *p = (int*)member;
    FREE(p);
}

static void
free_file_node(const void *key, void **value, void *cl)
{
    (void)key;
    (void)cl;

    Set_T lines = *(Set_T*)value;
    Set_map(lines, free_line, NULL);
    Set_free(&lines);
}

static void
free_id_node(const void *key, void **value, void *cl)
{
    (void)key;
    (void)cl;
    Table_T files = *(Table_T*)value;
    Table_map(files, free_file_node, NULL);
    Table_free(&files);
}

static void
destroy(Table_T *identifiers)
{
#ifdef DESTROY_BY_MAP
    Table_map(*identifiers, free_id_node, NULL);
    printf("Destroy by map\n");

#else
    void **id_arr = Table_toArray(*identifiers, NULL);
    for (int i = 0; id_arr[i]; i += 2) {
        Table_T files = id_arr[i+1];
        void **file_arr = Table_toArray(files, NULL);

        for (int j = 0; file_arr[j]; j += 2) {
            Set_T lines = file_arr[j+1];
            void **line_arr = Set_toArray(lines, NULL);

            for (int k = 0; line_arr[k]; k++)
                FREE(line_arr[k]);
            FREE(line_arr);
            Set_free(&lines);
        }
        FREE(file_arr);
        Table_free(&files);
    }
    FREE(id_arr);
    printf("Destroy by array\n");
#endif
    
    Table_free(identifiers);
}

int main(int argc, char **argv)
{
    Table_T identifiers = Table_new(0, NULL, NULL);
    for (int i = 1; i < argc; i++) {
        FILE *fp = fopen(argv[i], "r");
        if (fp == NULL) {
            fprintf(stderr, "Fail to open file:%s\n", argv[i]);
            continue;
        }
        xref(argv[i], fp, identifiers);
    }
    if (argc == 1)
        xref(NULL, stdin, identifiers);

    dealresult(identifiers);
    destroy(&identifiers);
    return 0;
}
